package com.yiba.vpn.proxy;

import android.annotation.SuppressLint;
import android.text.TextUtils;
import android.util.Log;

import com.yiba.vpn.core.VPNNode;
import com.yiba.vpn.protocal.tunnel.Config;
import com.yiba.vpn.protocal.tunnel.httpconnect.HttpConnectConfig;
import com.yiba.vpn.protocal.tunnel.shadowsocks.ShadowsocksConfig;
import com.yiba.vpn.utils.ChinaIpMaskManager;
import com.yiba.vpn.utils.CommonMethods;
import com.yiba.vpn.utils.logger.LogUtil;

import java.io.FileInputStream;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class ProxyConfig {
    public static final ProxyConfig Instance = new ProxyConfig();
    public final static boolean IS_DEBUG = true;
    public static String AppInstallID;
    public static String AppVersion;
    public static final int FAKE_NETWORK_IP = CommonMethods.ipStringToInt("10.231.0.0");
    public static final int FAKE_NETWORK_MASK = CommonMethods.ipStringToInt("255.255.0.0");

    ArrayList<IPAddress> m_IpList;
    ArrayList<IPAddress> m_DnsList;
    ArrayList<IPAddress> m_RouteList;
    public ArrayList<Config> m_ProxyList;
    HashMap<String, Boolean> m_DomainMap;

    public boolean globalMode = false; //全局模式

    int m_dns_ttl;
    String m_welcome_info;
    String m_session_name;
    String m_user_agent;
    boolean m_outside_china_use_proxy = true;
    boolean m_isolate_http_host_header = true;//隔离http_host_header
    int m_mtu;
    Timer m_Timer;

    public class IPAddress {
        public final String Address;
        public final int PrefixLength;

        public IPAddress(String address, int prefixLength) {
            this.Address = address;
            this.PrefixLength = prefixLength;
        }

        public IPAddress(String ipAddresString) {
            String[] arrStrings = ipAddresString.split("/");
            String address = arrStrings[0];
            int prefixLength = 32;
            if (arrStrings.length > 1) {
                prefixLength = Integer.parseInt(arrStrings[1]);
            }
            this.Address = address;
            this.PrefixLength = prefixLength;
        }

        @SuppressLint("DefaultLocale")
        @Override
        public String toString() {
            return String.format("%s/%d", Address, PrefixLength);
        }

        @Override
        public boolean equals(Object o) {
            if (o == null) {
                return false;
            } else {
                return this.toString().equals(o.toString());
            }
        }
    }

    public ProxyConfig() {
        m_IpList = new ArrayList<>();
        m_DnsList = new ArrayList<>();
        m_RouteList = new ArrayList<>();
        m_ProxyList = new ArrayList<>();
        m_DomainMap = new HashMap<>();
        m_Timer = new Timer();
        m_Timer.schedule(m_Task, 120000, 120000);//每两分钟刷新一次。
    }

    TimerTask m_Task = new TimerTask() {
        @Override
        public void run() {
            refreshProxyServer();//定时更新dns缓存
        }

        //定时更新dns缓存
        void refreshProxyServer() {
            try {
                for (int i = 0; i < m_ProxyList.size(); i++) {
                    try {
                        Config config = m_ProxyList.get(0);
                        InetAddress address = InetAddress.getByName(config.ServerAddress.getHostName());
                        if (address != null && !address.equals(config.ServerAddress.getAddress())) {
                            config.ServerAddress = new InetSocketAddress(address, config.ServerAddress.getPort());
                        }
                    } catch (Exception e) {
                    }
                }
            } catch (Exception e) {

            }
        }
    };


    public static boolean isFakeIP(int ip) {
        return (ip & ProxyConfig.FAKE_NETWORK_MASK) == ProxyConfig.FAKE_NETWORK_IP;
    }

    public Config getDefaultProxy() {
        if (m_ProxyList.size() > 0) {
            return m_ProxyList.get(0);
        } else {
            return null;
        }
    }

    public Config getDefaultTunnelConfig(InetSocketAddress destAddress) {
        return getDefaultProxy();
    }

    public IPAddress getDefaultLocalIP() {
        //这里后台每一次换主机 没人维护ip字段，我这里为了防止空指针，做了本地处理，每一次获取自己的私网
        if (m_IpList.size() > 0) {
            if (TextUtils.isEmpty(m_IpList.get(0).Address)) {
                return new IPAddress("10.8.0.2", 32);
            } else {
                return m_IpList.get(0);
            }
        } else {
            return new IPAddress("10.8.0.2", 32);
        }
    }

    public ArrayList<IPAddress> getDnsList() {
        return m_DnsList;
    }

    public ArrayList<IPAddress> getRouteList() {
        return m_RouteList;
    }

    public int getDnsTTL() {
        if (m_dns_ttl < 30) {
            m_dns_ttl = 30;
        }
        return m_dns_ttl;
    }

    public String getWelcomeInfo() {
        return m_welcome_info;
    }

    public String getSessionName() {
        if (m_session_name == null) {
            if (getDefaultProxy() != null) {
                m_session_name = getDefaultProxy().ServerAddress.getHostName();
            } else {
                return "dbq";
            }
        }
        return m_session_name;
    }

    public String getUserAgent() {
        if (m_user_agent == null || m_user_agent.isEmpty()) {
            m_user_agent = System.getProperty("http.agent");
        }
        return m_user_agent;
    }

    public int getMTU() {
        if (m_mtu > 1400 && m_mtu <= 20000) {
            return m_mtu;
        } else {
            return 20000;
        }
    }

    /**
     * todo
     * 针对微信支付和支付宝支付，不走代理
     *
     * @param domain
     * @return
     */
    private Boolean getDomainState(String domain) {
        domain = domain.toLowerCase();
        while (domain.length() > 0) {
            Boolean stateBoolean = m_DomainMap.get(domain);
            if (stateBoolean != null) {
                return stateBoolean;
            } else {
                int start = domain.indexOf('.') + 1;
                if (start > 0 && start < domain.length()) {
                    domain = domain.substring(start);
                } else {
                    return null;
                }
            }
        }
        return null;
    }

    /**
     * 代理策略：1.先匹配HOST,如果Host匹配上，直接proxy
     * 2.如果服务器给我们的是10.1.1.1/2 Ip段，我们在匹配IP段，如果在IP段里面就直接proxy
     *
     * @param host
     * @param ip
     * @return
     */
    public boolean needProxy(String host, int ip) {
        if (globalMode) {
            return true;
        }
        //TODO 2018-04-24，关于google相关的地址不再代理。杨海写，要是挂了算我的。
        if (host.contains("stripe")) {
            Log.i("url", host);
            return false;
        }
        if (host != null) {
            Boolean stateBoolean = getDomainState(host);
            if (stateBoolean != null) {
                return stateBoolean.booleanValue();
            }
        }
        if (isFakeIP(ip))
            return true;
        if (ip != 0) {
            //针对外翻项目 需要排除我们国外的DNS解析的服务器,后期这里需要优化
            if (CommonMethods.ipIntToString(ip).equals("54.186.167.173")) {
                return false;
            }
            return !ChinaIpMaskManager.isIPInChina(ip);
        }
//        if (m_outside_china_use_proxy && ip != 0) {
//            return !ChinaIpMaskManager.isIPInChina(ip);
//        }
        return false;
    }

    public boolean isIsolateHttpHostHeader() {
        return m_isolate_http_host_header;
    }

//    private String[] downloadConfig(String url) throws Exception {
//        try {
//            HttpClient client = new DefaultHttpClient();
//            HttpGet requestGet = new HttpGet(url);
//
//            requestGet.addHeader("X-Android-MODEL", Build.MODEL);
//            requestGet.addHeader("X-Android-SDK_INT", Integer.toString(Build.VERSION.SDK_INT));
//            requestGet.addHeader("X-Android-RELEASE", Build.VERSION.RELEASE);
//            requestGet.addHeader("X-App-Version", AppVersion);
//            requestGet.addHeader("X-App-Install-ID", AppInstallID);
//            requestGet.setHeader("User-Agent", System.getProperty("http.agent"));
//            HttpResponse response = client.execute(requestGet);
//
//            String configString = EntityUtils.toString(response.getEntity(), "UTF-8");
//            String[] lines = configString.split("\\n");
//            return lines;
//        } catch (Exception e) {
//            throw new Exception(String.format("Download config file from %s failed.", url));
//        }
//    }

    private String[] readConfigFromFile(String path) throws Exception {
        StringBuilder sBuilder = new StringBuilder();
        FileInputStream inputStream = null;
        try {
            byte[] buffer = new byte[8192];
            int count = 0;
            inputStream = new FileInputStream(path);
            while ((count = inputStream.read(buffer)) > 0) {
                sBuilder.append(new String(buffer, 0, count, "UTF-8"));
            }
            return sBuilder.toString().split("\\n");
        } catch (Exception e) {
            throw new Exception(String.format("Can't read config file: %s", path));
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e2) {
                }
            }
        }
    }

    public void loadFromFile(InputStream inputStream) throws Exception {
        byte[] bytes = new byte[inputStream.available()];
        inputStream.read(bytes);
        loadFromLines(new String(bytes).split("\\r?\\n"));
    }

    public void loadFromUrl(String url) throws Exception {
        String[] lines = null;
        if (url.charAt(0) == '/') {
            lines = readConfigFromFile(url);
        } else {
            // lines = downloadConfig(url);
        }
        loadFromLines(lines);
    }

    public void loadFormSerializableObj(VPNNode.ConfigBean configBean) {
        m_IpList.clear();
        m_DnsList.clear();
        m_RouteList.clear();
        m_ProxyList.clear();
        m_DomainMap.clear();
        if (configBean != null) {
            addIPAddressToList(configBean.getIp(), m_IpList);//手机tunl私网地址
            addIPAddressToList(configBean.getDns(), m_DnsList);//DNS list
            addDomainToHashMap(configBean.getProxy_domain(), m_DomainMap, true);//domain&ip pool
            addDomainToHashMap(configBean.getDirect_domain(), m_DomainMap, false);//direct 路由
        }
    }


    protected void loadFromLines(String[] lines) throws Exception {

        m_IpList.clear();
        m_DnsList.clear();
        m_RouteList.clear();
        m_ProxyList.clear();
        m_DomainMap.clear();

        int lineNumber = 0;
        for (String line : lines) {
            lineNumber++;
            String[] items = line.split("\\s+");
            if (items.length < 2) {
                continue;
            }

            String tagString = items[0].toLowerCase(Locale.ENGLISH).trim();
            try {
                if (!tagString.startsWith("#")) {
                    if (LogUtil.OPEN_LOG)
                        System.out.println(line);

                    if (tagString.equals("ip")) {
                        addIPAddressToList(items, 1, m_IpList);
                    } else if (tagString.equals("dns")) {
                        addIPAddressToList(items, 1, m_DnsList);
                    } else if (tagString.equals("route")) {
                        addIPAddressToList(items, 1, m_RouteList);
                    } else if (tagString.equals("proxy")) {
                        addProxyToList(items, 1);
                    } else if (tagString.equals("direct_domain")) {
                        addDomainToHashMap(items, 1, false);
                    } else if (tagString.equals("proxy_domain")) {
                        addDomainToHashMap(items, 1, true);
                    } else if (tagString.equals("dns_ttl")) {
                        m_dns_ttl = Integer.parseInt(items[1]);
                    } else if (tagString.equals("welcome_info")) {
                        m_welcome_info = line.substring(line.indexOf(" ")).trim();
                    } else if (tagString.equals("session_name")) {
                        m_session_name = items[1];
                    } else if (tagString.equals("user_agent")) {
                        m_user_agent = line.substring(line.indexOf(" ")).trim();
                    } else if (tagString.equals("outside_china_use_proxy")) {
                        m_outside_china_use_proxy = convertToBool(items[1]);
                    } else if (tagString.equals("isolate_http_host_header")) {
                        m_isolate_http_host_header = convertToBool(items[1]);
                    } else if (tagString.equals("mtu")) {
                        m_mtu = Integer.parseInt(items[1]);
                    }
                }
            } catch (Exception e) {
                throw new Exception(String.format("config file parse error: line:%d, tag:%s, error:%s", lineNumber, tagString, e));
            }

        }

        //查找默认代理。
        if (m_ProxyList.size() == 0) {
            tryAddProxy(lines);
        }
    }

    private void tryAddProxy(String[] lines) {
        for (String line : lines) {
            Pattern p = Pattern.compile("proxy\\s+([^:]+):(\\d+)", Pattern.CASE_INSENSITIVE);
            Matcher m = p.matcher(line);
            while (m.find()) {
                HttpConnectConfig config = new HttpConnectConfig();
                config.ServerAddress = new InetSocketAddress(m.group(1), Integer.parseInt(m.group(2)));
                if (!m_ProxyList.contains(config)) {
                    m_ProxyList.add(config);
                    m_DomainMap.put(config.ServerAddress.getHostName(), false);
                }
            }
        }
    }

    public void addProxyToList(String proxyString) throws Exception {
        Config config = null;
        if (proxyString.startsWith("ss://")) {
            config = ShadowsocksConfig.parse(proxyString);
        } else {
            if (!proxyString.toLowerCase().startsWith("http://")) {
                proxyString = "http://" + proxyString;
            }
            config = HttpConnectConfig.parse(proxyString);
        }
        if (!m_ProxyList.contains(config)) {
            m_ProxyList.add(config);
            m_DomainMap.put(config.ServerAddress.getHostName(), false);
        }
    }

    private void addProxyToList(String[] items, int offset) throws Exception {
        for (int i = offset; i < items.length; i++) {
            addProxyToList(items[i].trim());
        }
    }

    private void addDomainToHashMap(String[] items, int offset, Boolean state) {
        for (int i = offset; i < items.length; i++) {
            String domainString = items[i].toLowerCase().trim();
            if (domainString.charAt(0) == '.') {
                domainString = domainString.substring(1);
            }
            m_DomainMap.put(domainString, state);
        }
    }

    private void addDomainToHashMap(List<String> proxy_domain, HashMap<String, Boolean> m_domainMap, boolean state) {
        if (proxy_domain != null) {
            for (String domain : proxy_domain) {
                String domainString = domain.toLowerCase().trim();
                //判断是否是ip还是domain
                if (domainString.matches(".*[a-zA-z].*")) {
                    if (domainString.charAt(0) == '.') {
                        domainString = domainString.substring(1);
                    }
                    m_domainMap.put(domainString, state);
                } else {
                    ChinaIpMaskManager.loadFromObject(domainString);
                }
            }
        }
    }

    private boolean convertToBool(String valueString) {
        if (valueString == null || valueString.isEmpty())
            return false;
        valueString = valueString.toLowerCase(Locale.ENGLISH).trim();
        if (valueString.equals("on") || valueString.equals("1") || valueString.equals("true") || valueString.equals("yes")) {
            return true;
        } else {
            return false;
        }
    }


    private void addIPAddressToList(String[] items, int offset, ArrayList<IPAddress> list) {
        for (int i = offset; i < items.length; i++) {
            String item = items[i].trim().toLowerCase();
            if (item.startsWith("#")) {
                break;
            } else {
                IPAddress ip = new IPAddress(item);
                if (!list.contains(ip)) {
                    list.add(ip);
                }
            }
        }
    }

    /**
     * 针对IP的添加
     *
     * @param item
     * @param list
     */
    private void addIPAddressToList(String item, ArrayList<IPAddress> list) {
        IPAddress ip = new IPAddress(item, 32);
        if (!list.contains(ip)) {
            list.add(ip);
        }
    }

    /**
     * 针对DNS和proxy的处理
     *
     * @param items
     * @param list
     */
    private void addIPAddressToList(List<String> items, ArrayList<IPAddress> list) {
        if (!items.isEmpty()) {
            for (String ss : items) {
                IPAddress ip = new IPAddress(ss);
                if (!list.contains(ip)) {
                    list.add(ip);
                }
            }
        }
    }
}
